#!/usr/bin/perl -w

# $Id: backup 463 2008-06-25 04:52:20Z ogawa $

package MT::Tool::Backup;
use strict;
use warnings;

use lib qw( lib extlib );
use base qw( MT::Tool );

our $VERSION = '0.1';

sub usage { '[--dir <dir>] [--blog_ids <id1,id2,...>] [--size <num>] [--archive=zip|tgz]' }

sub help {
    return qq{
        backup $VERSION - A command-line tools for backup

        --dir <dir>
                Specify the destination directory. By default, the backup
                files are created under the directory, specified by the
                value of TempDir in your mt-config.cgi.
        --blog_ids <id1,id2,...>
                Specify the blogs to include this backup. By default,
                all blogs are included in the backup.
        --size <num>    
                Specify approximate file size (<num> kbytes) per backup file.
                If not specified, backup files are never divided.
        --archive=zip|tgz
                Specify the archive format. If not specified, this program
                just generates a set of backup files and does not create 
                a compressed archive file.

    };
}

my ($temp_dir, $blog_ids, $size, $archive);
sub options {
    return (
        'dir=s'      => \$temp_dir,
        'blog_ids=s' => \$blog_ids,
        'size=i'     => \$size,
        'archive=s'  => \$archive,
        );
}

sub main {
    my $class = shift;
    my ($verbose) = $class->SUPER::main(@_);

    $temp_dir ||= MT->config('TempDir');
    die "Can't open directory '$temp_dir'" unless -w $temp_dir;

    my @blog_ids;
    if ($blog_ids) {
        @blog_ids = grep { $_ =~ /^\d+$/ } ( split ',', $blog_ids );
        die "There're no blogs to backup" unless scalar @blog_ids;
    } else {
        die "There're no blogs to backup" unless MT->model('blog')->exist;
    }

    if ($archive) {
        $archive = lc $archive;
        require MT::Util::Archive;
        my @fmts = map { lc $_->{key} } MT::Util::Archive->available_formats();
        unless ( grep { $_ eq $archive } @fmts ) {
            die "Unsupported archive format: $archive";
        }
    }

    backup(\@blog_ids, $temp_dir, $size || 0, $archive, $verbose);
    1;
}

# This function is almostly copied from MT::CMS::Tools::backup()
use Symbol;

sub backup {
    my ($blog_ids, $temp_dir, $size, $archive, $verbose) = @_;

    my $enc = MT->config('PublishCharset') || 'utf-8';
    my @ts = gmtime(time);
    my $ts = sprintf "%04d-%02d-%02d-%02d-%02d-%02d", $ts[5] + 1900, $ts[4] + 1,
      @ts[ 3, 2, 1, 0 ];
    my $file = "Movable_Type-$ts" . '-Backup';

    require File::Temp;
    require File::Spec;
    use File::Copy;

    require MT::BackupRestore;
    my $count_term =
      $blog_ids && scalar( @$blog_ids )
      ? { class => '*', blog_id => $blog_ids }
      : { class => '*' };
    my $num_assets = MT->model('asset')->count($count_term);
    my $printer;
    my $splitter;
    my $finisher;
    my $progress = sub {
        my ($str, $id) = @_;
        print STDERR "$str\n" if $verbose;
    };

    if ( !( $size || $num_assets ) ) {
        my $filename = File::Spec->catfile( $temp_dir, $file . ".xml" );
        my $fh = gensym();
        open $fh, ">$filename";

        $printer =
          sub { my ($data) = @_; print $fh $data; return length($data); };
        $splitter = sub { };
        $finisher = sub {
            my ($asset_files) = @_;
            close $fh;
            if ($archive) {
                my $filepath = File::Spec->catfile( $temp_dir, "$file.$archive" );
                require MT::Util::Archive;
                my $arc = MT::Util::Archive->new($archive, $filepath);
                $arc->add_file( $temp_dir, "$file.xml" );
                $arc->add_string(
                        "<manifest xmlns='"
                      . MT::BackupRestore::NS_MOVABLETYPE()
                      . "'><file type='backup' name='$file.xml' /></manifest>",
                      "$file.manifest");
                $arc->close;
                # for safery, don't unlink before closing $arc here.
                unlink $filename;
            }
        };
    }
    else {
        my @files;
        my $filename = File::Spec->catfile( $temp_dir, $file . "-1.xml" );
        my $fh = gensym();
        open $fh, ">$filename";
        push @files, $file . "-1.xml";

        $printer =
          sub { my ($data) = @_; print $fh $data; return length($data); };
        $splitter = sub {
            my ($findex) = @_;
            print $fh '</movabletype>';
            close $fh;
            my $filename =
                File::Spec->catfile( $temp_dir, $file . "-$findex.xml" );
            $fh = gensym();
            open $fh, ">$filename";
            push @files, $file . "-$findex.xml";
            my $header .=
              "<movabletype xmlns='"
              . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n";
            $header = "<?xml version='1.0' encoding='$enc'?>\n$header"
              if $enc !~ m/utf-?8/i;
            print $fh $header;
        };
        $finisher = sub {
            my ($asset_files) = @_;
            close $fh;
            my $filename = File::Spec->catfile( $temp_dir, "$file.manifest" );
            $fh = gensym();
            open $fh, ">$filename";
            print $fh "<manifest xmlns='"
              . MT::BackupRestore::NS_MOVABLETYPE() . "'>\n";
            for my $name (@files) {
                print $fh "<file type='backup' name='$name' />\n";
            }
            for my $id ( keys %$asset_files ) {
                my $asset = $asset_files->{$id};
                my $name = $id . '-' . $asset->[2];
                my $tmp = File::Spec->catfile( $temp_dir, $name );
                unless ( copy( $asset->[1], $tmp ) ) {
                    print STDERR MT->translate(
                        'Copying file [_1] to [_2] failed: [_3]',
                        $asset->[1], $tmp, $!
                    ) if $verbose;
                    next;
                }
                print $fh "<file type='asset' name='"
                  . $asset->[2]
                  . "' asset_id='"
                  . $id
                  . "' />\n";
                push @files, $name;
            }
            print $fh "</manifest>\n";
            close $fh;
            push @files, "$file.manifest";

            if ($archive) {
                my $filepath = File::Spec->catfile( $temp_dir, "$file.$archive" );
                require MT::Util::Archive;
                my $arc = MT::Util::Archive->new($archive, $filepath);
                for my $f (@files) {
                    $arc->add_file( $temp_dir, $f );
                }
                $arc->close;
                # for safery, don't unlink before closing $arc here.
                for my $f (@files) {
                    unlink File::Spec->catfile( $temp_dir, $f );
                }
            }
        };
    }

    my @tsnow    = gmtime(time);
    my $metadata = {
        backup_by => __PACKAGE__,
        backup_on => sprintf(
            "%04d-%02d-%02dT%02d:%02d:%02d",
            $tsnow[5] + 1900,
            $tsnow[4] + 1,
            @tsnow[ 3, 2, 1, 0 ]
        ),
        backup_what    => join( ',', @$blog_ids ),
        schema_version => MT->config('SchemaVersion'),
    };

    MT::BackupRestore->backup( $blog_ids, $printer, $splitter, $finisher,
        $progress, $size * 1024,
        $enc, $metadata );
}

__PACKAGE__->main() unless caller;

1;
